package uk.ac.ebi.uniprot.wikipedia.retrievalServices;

import org.apache.log4j.Logger;
import uk.ac.ebi.kraken.interfaces.uniprot.DatabaseType;
import uk.ac.ebi.kraken.interfaces.uniprot.UniProtEntry;
import uk.ac.ebi.kraken.interfaces.uniprot.UniProtEntryType;
import uk.ac.ebi.kraken.storage.retrieval.EntryDataRetrieval;
import uk.ac.ebi.kraken.storage.search.SearchEngine;
import uk.ac.ebi.kraken.storage.search.SearchableDatabase;
import uk.ac.ebi.kraken.storage.search.queries.IndexQuery;
import uk.ac.ebi.kraken.storage.search.queries.IndexQueryBuilder;
import uk.ac.ebi.kraken.storage.search.results.KrakenSearchResult;
import uk.ac.ebi.kraken.storage.search.results.LuceneSearchResult;
import uk.ac.ebi.kraken.util.IndexField;
import uk.ac.ebi.uniprot.wikipedia.exceptions.NoSuchOrganismException;
import uk.ac.ebi.uniprot.wikipedia.exceptions.RetrievalException;
import uk.ac.ebi.uniprot.wikipedia.exceptions.ServiceException;
import uk.ac.ebi.uniprot.wikipedia.exceptions.UnexpectedException;
import uk.ac.ebi.uniprot.wikipedia.util.AccessionResolver;
import uk.ac.ebi.uniprot.wikipedia.util.GeneWikiOrganism;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


/**
 * Created with IntelliJ IDEA.
 * User: ljgarcia
 * Date: 11/06/12
 * Time: 15:23
 * To change this template use File | Settings | File Templates.
 */
public class UniProtRetrievalImpl implements UniProtRetrieval {
    private static Logger logger = Logger.getLogger(UniProtRetrievalImpl.class);
    private EntryDataRetrieval uniProtRetrieval;
    private SearchEngine uniProtSearchEngine;
    private IndexQueryBuilder indexQueryBuilder;

    @Override
    public void init() throws ServiceException {
        try {
            if (uniProtRetrieval != null && !uniProtRetrieval.isStarted()) {
                uniProtRetrieval.startUp();
            }

            if (uniProtSearchEngine != null && !uniProtSearchEngine.isStarted()) {
                uniProtSearchEngine.startUp();
            }

        } catch (Exception e) {
            logger.error("Unable to initialize the uniprot retrieval services " + e);
            throw new ServiceException("Unable to initialize the uniprot retrieval services", e);
        }
    }

    @Override
    public UniProtEntry getEntryByAccessionAndOrganism(String accessionOrId, GeneWikiOrganism organism) throws UnexpectedException, NoSuchOrganismException, RetrievalException, ServiceException {
        //IndexQuery query = indexQueryBuilder.buildContentsQuery(accessionOrId + " and " + IndexField.ORGANISM_NAMES + ":" + organism.getOrganism(), SearchableDatabase.UNIPROT);
        UniProtEntry uniProtEntry =  null;
        try {
            if (AccessionResolver.isUniProtIdentifier(accessionOrId)) {

                /* Try to match against the UniProt accession matcher.  This method returns null
                 if there is no match.*/
                String[] accessionParts = AccessionResolver.parseUniprotAccession(accessionOrId);

                if (accessionParts != null) {
                    if (accessionParts[1] == null || accessionParts[1].length() == 0) {
                        // No isoform, just a plain UniProt accession so retrieve directly through the entry retrieval
                        uniProtEntry = (UniProtEntry) uniProtRetrieval.getEntry(accessionOrId);
                    } else {
                        // Isoform, so use the query service.
                        uniProtEntry = getEntryFromQueryServiceBasedOnId(accessionOrId);
                    }
                } else {
                    // UniProt ID, so use the query service.
                    uniProtEntry = getEntryFromQueryServiceBasedOnId(accessionOrId);
                }
            } else {
                throw new ServiceException("This identifier is not a valid UniProt primary entry: " + accessionOrId);
            }
            try {
                if (!uniProtEntry.getOrganism().getCommonName().getValue().equalsIgnoreCase(organism.getOrganism())) {
                    throw new NoSuchOrganismException("The retrieved organism "
                        + uniProtEntry.getOrganism().getCommonName().getValue() + " for the protein " + uniProtEntry.getPrimaryUniProtAccession().getValue()
                        + " does no correspond to the requested one, i.e., " + organism.getOrganism());
                }
            } catch (NullPointerException e) {//something in the organism name failed
                throw new RetrievalException("The organism or common name for " + accessionOrId + " cannot be retrieved: " + e.getMessage());
            }
        } catch (ServiceException e) {
            throw e;
        } catch (NoSuchOrganismException e) {
            throw e;
        } catch (RetrievalException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            throw new UnexpectedException("Unexpected error retrieving UniProt entry " + accessionOrId, e);
        }

        return uniProtEntry;
    }

    @Override
    public List<UniProtEntry> getEntryByGeneIdAndOrganism(String geneId, GeneWikiOrganism organism, boolean onlyReviewed) {
        List<UniProtEntry> listUniProtEntry = new ArrayList<UniProtEntry>();
        //retrieve all entries that has a geneid crossreference with the given value
        IndexQuery query = indexQueryBuilder.buildContentsQuery(
                //smartSearch.getSmartQuery(IndexField.DATABASE_CROSSREFERENCES.getValueForType(DatabaseType.GENEID) + ":" + term)
                IndexField.DATABASE_CROSSREFERENCES.getValueForType(DatabaseType.GENEID) + ":" + geneId
                , SearchableDatabase.UNIPROT);
        //look for the entries in lucene
        query.setBitType(IndexQuery.BitSetType.HIBERNATE);
        query.setBitSetRequired(true);

        KrakenSearchResult result = uniProtSearchEngine.search(query);

        Iterator<UniProtEntry> it = uniProtRetrieval.getEntries(result.getBitSet());
        while(it.hasNext()) {
            UniProtEntry entry = it.next();
            //UniProtEntryType
            if (entry.getOrganism().getCommonName().getValue().equalsIgnoreCase(organism.getOrganism())) {
                if (!onlyReviewed) {
                    listUniProtEntry.add(entry);
                } else if (onlyReviewed && entry.getType().equals(UniProtEntryType.SWISSPROT) ) {
                    listUniProtEntry.add(entry);
                }

            }
        }
        return listUniProtEntry;
    }

    /**
     * For queries that are not simple accessions, use the entry iterator on the UniProt query service object.
     *
     * @param segmentId - String containing the entry identifier
     * @return a uniprot entry, null if no entry is found
     * @throws ServiceException - occurs when the provided identifier is not found within the current data source
     */
    private UniProtEntry getEntryFromQueryServiceBasedOnId(String segmentId) throws ServiceException {
        try {
            //retrieve all entries that match exactly to the segmentId
            IndexQuery query = indexQueryBuilder.buildIdentifierQuery(segmentId, SearchableDatabase.UNIPROT);
            //look for the entries in lucene
            //query.setBitType(IndexQuery.BitSetType.HIBERNATE);
            query.setBitSetRequired(false);

            LuceneSearchResult result = (LuceneSearchResult) uniProtSearchEngine.search(query);

            Iterator<UniProtEntry> it = uniProtRetrieval.getEntries();
            //Iterator<UniProtEntry> it = uniProtRetrieval.getEntries(result.getBitSet());
            if (it.hasNext()) {
                return it.next();
            } else
                throw new ServiceException("Reference does not exist: " + segmentId);
            /*
              Seeing as we are using an exact match query, there will be no more than entry per query.
              Meaning that we do not have to check if there are more than one element in the itrerator
            */
        } catch (ServiceException e) {
            throw e;
        } catch (Exception e) {
            throw new ServiceException("Error retrieving UniProt entry", e);
        }
    }

    @Override
    public void destroy() {
        if (uniProtRetrieval != null && uniProtRetrieval.isStarted()) {
            uniProtRetrieval.shutdown();
        }
        if (uniProtSearchEngine != null && uniProtSearchEngine.isStarted()) {
            uniProtSearchEngine.shutdown();
        }
    }

    public void setUniProtRetrieval(EntryDataRetrieval uniProtRetrieval) {
        this.uniProtRetrieval = uniProtRetrieval;
    }

    public void setUniProtSearchEngine(SearchEngine uniProtSearchEngine) {
        this.uniProtSearchEngine = uniProtSearchEngine;
    }

    public void setIndexQueryBuilder(IndexQueryBuilder indexQueryBuilder) {
        this.indexQueryBuilder = indexQueryBuilder;
    }

    /*public static void main(String[] args) throws ServiceException {
        UniProtRetrieval retrieval = (UniProtRetrievalImpl) SpringContextLoader.getInstance().getBean("uniProtRetrieval4Wiki");
        List<UniProtEntry> entries = retrieval.getEntryByGeneIdAndOrganism("7529", GeneWikiOrganism.HUMAN);
        for (UniProtEntry entry:entries) {
            System.out.println(entry.getPrimaryUniProtAccession().getValue());
            System.out.println(entry.getOrganism().getCommonName().getValue());
            for (DatabaseCrossReference gene: entry.getDatabaseCrossReferences(DatabaseType.GENEID)) {
                GeneId geneImpl = (GeneId)gene;
                System.out.println(geneImpl.getGeneIdAccessionNumber());
            }
        }
    }*/
}
